#ifndef _v3_H_
#define _v3_H_

class v2;


class v3
{
public:
    // member functions
	v3 Set(float fX, float fY, float fZ) { x=fX; y=fY; z=fZ; return *this; };
	v3 Clear() { return Set(0.0f, 0.0f, 0.0f); };
  inline v3& operator=(const v3 &V);
  inline v3 operator+(const v3 &V) const;
  inline v3 operator-(const v3 &V) const;
  inline v3 operator*(float f) const;
  inline v3 Cross(const v3 &V) const;
  inline v3 Scale(const v3 &scale) const;
  inline float Magnitude() const;
  inline float MagnitudeSquared() const;
  inline float Normalize();	///< Normalize in place; returns original length
	inline bool IsZero(float fEpsilon=0.0f);
	inline bool IsOne(float fEpsilon=0.001f);
	inline v3 Lerp(const v3 &V, float fBlend) const;
	inline float Dot(const v3 &V) const;
	inline v3 FindAbitraryPerpendicular() const;
	inline v3 FindPerpendicular(const v3 &Reference) const;
	inline v3 RemoveComponent(const v3 &Reference) const;

	// a sort of constructor
	static v3 Make(float fX, float fY, float fZ) { v3 v; v.Set(fX, fY, fZ); return v; };

    // members
	float x;
	float y;
	float z;
};


#include "OMath.h"
#include "v2.h"

v3& v3::operator=(const v3 &V)
{
	x=V.x;
	y=V.y;
	z=V.z;

	return *this;
}


v3 v3::operator+(const v3 &V) const
{
	v3 R;

	R.x=x+V.x;
	R.y=y+V.y;
	R.z=z+V.z;

	return R;
}


v3 v3::operator-(const v3 &V) const
{
	v3 R;

	R.x=x-V.x;
	R.y=y-V.y;
	R.z=z-V.z;

	return R;
}


v3 v3::operator*(float f) const
{
	v3 R;

	R.x=x*f;
	R.y=y*f;
	R.z=z*f;

	return R;
}


v3 v3::Cross(const v3 &V) const
{
  v3 R;
  return R.Set(
    (y * V.z) - (z * V.y),
    (z * V.x) - (x * V.z),
    (x * V.y) - (y * V.x)
    );
}


v3 v3::Scale(const v3 &scale) const
{
  v3 R;
  return R.Set(
    x*scale.x,
    y*scale.y,
    z*scale.z
    );
}


float v3::Magnitude() const
{
	return OMath::Sqrt(x*x+y*y+z*z);
}


float v3::MagnitudeSquared() const
{
	return x*x+y*y+z*z;
}


float v3::Normalize()
{
	float fMag;
	float fOOMag;

	fMag=Magnitude();
	if(fMag)
	{
		fOOMag=1.0f/fMag;
		x*=fOOMag;
		y*=fOOMag;
		z*=fOOMag;
	}

	return fMag;
}


v3 v3::Lerp(const v3 &V, float fBlend) const
{
	return *this + (V - *this) * fBlend;
}


float v3::Dot(const v3 &V) const
{
	return x*V.x+y*V.y+z*V.z;
}


v3 v3::FindAbitraryPerpendicular() const
{
	if(this->MagnitudeSquared()==0.0f)
	{
		// null vector; let's use the YAxis for fun
		// this is really an error, I suppose
		return OMath::YAxis;
	}

	float AbsY;
	v3 Perp;
	AbsY=OMath::Abs(this->y);
	if(AbsY>OMath::Abs(this->x) && AbsY>OMath::Abs(this->z))
	{
		// yaxis is major axis, use XAxis to cross with to find perp
		Perp=this->Cross(OMath::XAxis);
	}
	else
	{
		// yaxis is not major axis, use YAxis to cross with to find perp
		Perp=this->Cross(OMath::YAxis);
	}
	Perp.Normalize();
	return Perp;
}


v3 v3::FindPerpendicular(const v3 &_Reference) const
{
	v3 Source(*this);
	v3 Ref(_Reference);
	float fDot;

	Source.Normalize();
	Ref.Normalize();
	fDot=Source.Dot(Ref);

	// see if forward and up look relatively good
	if(fDot<0.001f && fDot>-0.001f)
	{
		// these vectors are pretty prependicular
		return Ref;
	}

//	v3 P;

	if(fDot<-0.9999f || fDot>0.9999f)
	{
		// vectors are the same - it's time for us to make a new reference vector
		if(Source.y>0.99f || Source.y<-0.99f)
		{
			// this vector is very much in the y-axis direction - use Z axis for reference
			Ref=OMath::ZAxis;
		}
		else
		{
			Ref=OMath::YAxis;
		}
		fDot=Source.Dot(Ref);
	}

	Ref=Ref-(Source*(1.0f/fDot));
	Ref.Normalize();

	return Ref;
}


v3 v3::RemoveComponent(const v3 &_Reference) const
{
	v3 Source(*this);
	v3 Ref(_Reference);
	float fDot;

	Ref.Normalize();
	fDot=Source.Dot(Ref);

	return Source-Ref*fDot;
}


bool v3::IsZero(float fEpsilon)
{
	float fEE;
	float fMM;

	fEE=fEpsilon*fEpsilon;
	fMM=v3::MagnitudeSquared();

	return fMM<=fEE;
}


bool v3::IsOne(float fEpsilon)
{
	float fEE;
	float fMM;

	fEE=fEpsilon*fEpsilon;
	fMM=v3::MagnitudeSquared();

	return fMM>=1.0f-fEE && fMM<=1.0f+fEE;
}

#endif
